import IndividuoMultiobjetivo
import operator
import random
import matplotlib.pyplot as ppl
import IndividuoReal
import IndividuoRealTest4
import IndividuoTest5

#----------------------------------------------------------------------
def Dominancia(individuo1, individuo2):
  (ind1f1, ind1f2) = individuo1.GetFitnesVals()
  (ind2f1, ind2f2) = individuo2.GetFitnesVals()
  if (ind1f1 < ind2f1) & (ind1f2 < ind2f2):
    return 1
  elif (ind2f1 < ind1f1) & (ind2f2 < ind1f2):
    return 2

  return 0

#----------------------------------------------------------------------
def ParetoRankR(populationToEval, rank, orden):
  dominados = []
  noDominados = []
  n =0
  # Recorremos los individuos
  for indiv1 in populationToEval:
    n += 1
    dominado = False
    # Lo comparamos con el resto
    for indiv2 in populationToEval: #[n:]
      dom = Dominancia(indiv1, indiv2)

      #Si el individuo2 domina ->  indiv1 -> dominados 
      if dom == 2:
        dominados += [indiv1]
        dominado = True
        break

    if not dominado:
      rank[indiv1] = orden

  if len(dominados) > 0:
    ParetoRankR(dominados, rank, orden + 1)

  return rank

#----------------------------------------------------------------------
def EsDominado(populationToEval, individuo):
  # Lo comparamos con el resto
  for indiv2 in populationToEval:
    dom = Dominancia(individuo, indiv2)

    #Si el individuo2 domina ->  indiv1 -> dominados 
    if dom == 2:
      dominados += [indiv1]
      return True
      break

    return False



#----------------------------------------------------------------------
def ParetoRank(populationToEval):
  rank = {}
  return ParetoRankR(populationToEval, rank, 0)



#----------------------------------------------------------------------
#------------------------------- Clase NPGA ---------------------------
#----------------------------------------------------------------------
class NPGA():
    """description of class"""

    poblacion = []
    numeroIndividuos = 20
    radio = 2
    rank = {}
    numParametros = 30
    numCodificacion = 30
    maxVal = 1.0
    tournamentSize = 2

    #----------------------------------------------------------------------
    def __init__(self, numParametros, numCodificacion, maxVal, radio, tournamentSize, setComparacion, alfa, useRealIndivs, useRankAsDomination):
      self.poblacion = []
      self.rank = {}
      self.numParametros = numParametros
      self.numCodificacion = numCodificacion
      self.maxVal = maxVal
      self.radio = radio
      self.tournamentSize = tournamentSize
      self.setComparacion  = setComparacion
      self.alfa = alfa
      self.useRealIndivs = useRealIndivs
      self.useRankAsDomination = useRankAsDomination

    #----------------------------------------------------------------------
    def GenerarPoblacion(self, _funcionValores, numeroIndividuos, useTest4Indivs, useTest5Indivs):
      self.poblacion = []
      self.numeroIndividuos = numeroIndividuos
      for it in range(self.numeroIndividuos):
        if useTest4Indivs:
          ind = IndividuoRealTest4.IndividuoReal(_funcionValores, self.numParametros, self.alfa, 1.0, 5.0)
        elif useTest5Indivs:
          ind = IndividuoTest5.IndividuoMultiobjetivo(_funcionValores, 11, 5, 1.0)
        else:
          if self.useRealIndivs:
            ind = IndividuoReal.IndividuoReal(_funcionValores, self.numParametros, self.alfa, self.maxVal)
          else:
            ind = IndividuoMultiobjetivo.IndividuoMultiobjetivo(_funcionValores, self.numParametros, self.numCodificacion, self.maxVal)

        ind.RandomInit()
        self.poblacion.append(ind)
            
      return self.poblacion


    #----------------------------------------------------------------------
    def TamanioNicho(self, individuo1, radio):
      nicho1 = 0
      for indinv in self.poblacion:
        if individuo1.Distancia(indinv) < radio:
          nicho1 += 1

      return nicho1

    
    #----------------------------------------------------------------------
    def SeleccionPorNicho(self, individuo1, individuo2, radio):
      m1 = 0.0
      m2 = 0.0
      for indinv in self.poblacion:
        d1 = individuo1.Distancia(indinv)
        d2 = individuo2.Distancia(indinv)
        if d1 < radio:
          m1 += (1 - (d1 / radio)**2)
        if d2 < radio:
          m2 += (1 - (d2 / radio)**2)
    
      if m1 < m2:
        return individuo1
      else:
        return individuo2
      
    #----------------------------------------------------------------------
    def SeleccionClaseDeEquivalencia(self, individuo1, individuo2, radio):
      m1 = 0.0
      m2 = 0.0
      for indinv in self.poblacion:
        d1 = individuo1.Distancia(indinv)
        d2 = individuo2.Distancia(indinv)
        if d1 < radio:
          m1 += 1
        if d2 < radio:
          m2 += 1
    
      if m1 < m2:
        return individuo1
      else:
        return individuo2


    #----------------------------------------------------------------------
    def SeleccionPorNichoMulti(self, listIndividuos):
      mi = {}
      for candidate in listIndividuos:
        mi[candidate] = 0.0

      for indinv in self.poblacion:
        for candidate in listIndividuos:
          d = candidate.Distancia(indinv)
          if d < self.radio:
            mi[candidate] += (1 - d / self.radio)

      best = listIndividuos[0]
      bestVal = self.numeroIndividuos
      for candidate in listIndividuos:
        if mi[candidate] < bestVal:
          best = candidate
          bestVal = mi[candidate]

      return best



    #----------------------------------------------------------------------
    def TorneoMulti(self, individuo1, individuo2):
      if self.rank[individuo1] < self.rank[individuo2]:
          seleccionado = individuo1
      elif self.rank[individuo2] < self.rank[individuo1]:
        seleccionado = individuo2
      else:
        # Seleccion por nicho
        seleccionado = self.SeleccionPorNicho(individuo1, individuo2, self.radio)

      return seleccionado
    
    #----------------------------------------------------------------------
    def TorneoDominanciaSet(self, listInidivduos):
      """ Se selecciona el individuo que domina a todos en un set de individuos. Si no, por nicho """
      for indiv in listInidivduos:
        candidato = True
        for indiv2 in listInidivduos:
          if indiv == indiv2:
            continue

          if self.rank[indiv] >= self.rank[indiv2]:
            candidato = False
            break
          
        if candidato:
          return indiv

      return self.SeleccionPorNichoMulti(listInidivduos)

    #----------------------------------------------------------------------
    def CompararContraSet(self, individuo1, individuo2, setInidividuos):
      """ Se selecciona el individuo de los dos dados que domina al set de individuos. Si no, por nicho entre los dos"""
      esDominado1 = False
      esDominado2 = False
      for indiv in setInidividuos:
        if Dominancia(individuo1, indiv) is 2:
          esDominado1 |= True

        if Dominancia(individuo2, indiv) is 2:
          esDominado2 |= True
      
      if esDominado2 and not esDominado1:
        return individuo1
          
      if esDominado1 and not esDominado2:
        return individuo2

#      return self.SeleccionPorNicho(individuo1, individuo2, self.radio)
      return self.SeleccionClaseDeEquivalencia(individuo1, individuo2, self.radio)
    
    #----------------------------------------------------------------------
    def SeleccionMulti(self):
      """Torneo donde salen los emparejamientos para el cruce para Multiobjetivo"""
      random.shuffle(self.poblacion)
#     return self.TorneoDominanciaSet((self.poblacion[:self.tournamentSize]))
      if self.useRankAsDomination:
        return self.TorneoMulti(self.poblacion[0], self.poblacion[1])
      else:
        return self.CompararContraSet(self.poblacion[0], self.poblacion[1], self.poblacion[2:(self.setComparacion+2)])

    
    #----------------------------------------------------------------------
    def Step(self):
      # Evaluar poblacion
      # Establecer ranking de pareto
      # Torneo
      #   Si Ganador -> newPopulation
      #   Sino -> Contar nichos, quedarse con el menos denso
      # 
      # Cruce
      # Mutacion

      #Ranking
      self.rank.clear()
      self.rank = ParetoRank(self.poblacion)
#      MostrarPoblacion(self.poblacion, self.rank)

      # Seleccion
      conjuntoSeleccionado = []
      while len(conjuntoSeleccionado) < len(self.poblacion):
        conjuntoSeleccionado += [self.SeleccionMulti()]

      descendencia = []
      matriz = []
      suma = 0.0

      # Cruce
      for it in range(len(conjuntoSeleccionado)/2):
        (hijo1, hijo2) = conjuntoSeleccionado[it*2].CruzarCromosomas(conjuntoSeleccionado[it*2 + 1])
        descendencia.append(hijo1)
        descendencia.append(hijo2)

      #for it in range(len(conjuntoSeleccionado)):
      #  suma += conjuntoSeleccionado[it].GetSharedFitness(self.TamanioNicho(conjuntoSeleccionado[it], self.radio))
      #  matriz += [suma]


      #while len(descendencia) < len(self.poblacion):
      #  val = random.randrange(int(matriz[-1]))
      #  elem1 = 0
      #  for it in range(len(conjuntoSeleccionado)):
      #    if matriz[it] > val:
      #      elem1 = it
      #      break

      #  val = random.randrange(int(matriz[-1]))
      #  elem2 = 0
      #  for it in range(len(conjuntoSeleccionado)):
      #    if matriz[it] > val:
      #      elem2 = it
      #      break
      
      #  (hijo1, hijo2) = conjuntoSeleccionado[elem1].Cruzar(conjuntoSeleccionado[elem2])
      #  descendencia.append(hijo1)
      #  descendencia.append(hijo2)
      
      # Opcional Mutacion
      posIndiv = random.randrange(len(self.poblacion)-1)
      descendencia[posIndiv].Mutar()
      self.poblacion = descendencia
      
      # Reemplazo

      # Esta forma de reemplazo es el NSGA!!!
      #for indiv in self.poblacion:
      #  indiv.currentRank = self.rank[indiv]
        
      descRank = ParetoRank(descendencia)
      for indiv in descendencia:
        indiv.currentRank = descRank[indiv]

      #allPop = self.poblacion + descendencia

      #sorted_todos = sorted(allPop, key=operator.attrgetter('currentRank'))
      #self.poblacion = sorted_todos[:self.numeroIndividuos]
      

      return self.poblacion

